package com.tom.sen.base;

import ch.qos.logback.classic.pattern.MessageConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import cn.hutool.core.collection.CollUtil;
import com.tom.sen.util.SensitiveInfoUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.util.*;
import java.util.regex.Pattern;

@Slf4j
public class SensitiveDataConverter extends MessageConverter {

    /**
     * 前分割符， 通过判断key前面那个字符，来判断此key是单独的，而不是某个单词的一部分，如: abccustomId    此customId不是key (customId      此customId是key abc
     * customId   此customId是key ，customId     此customId是key
     * <p>
     * 匹配 【空格】、【制表符】、【换行符】、【回车符】、【(】、【（】、【{】、【,】、【，】、【"】
     */
    protected static final Pattern PRE_DELIMITER_PATTERN = Pattern.compile("[\\s\\t\\r\\n(（{,，\"]+");

    /** 空格匹配 */
    protected static final Pattern SPACE_PATTERN = Pattern.compile("\\s*");


    public static final String NULL = "null";

    public static final String OBJECT_PREFIX_SIGN = "{";

    public static final String JSON_STRING_SIGN = "\"{";

    public static final String ARRAY_PREFIX_SIGN = "[";

    public static final String ENUM_SPLIT_SIGN = "__";

    public static final String DOT_SIGN = ".";

    public static final String BACKSLASH_QUOTATION_SIGN = "\\\"";

    public static final String QUOTATION_SIGN = "\"";

    /**
     * 需要脱敏的类前缀 如果不填写则全部脱敏
     */
    private static final Map<String,SensitiveType> keys = new HashMap();

    /**
     * 需要脱敏的包
     */
    private static final Set<String> includePackages=new HashSet<>();
    /**
     * 是否脱敏日志
     */
    private static boolean enable;


    public static void addkeys(Map<String,SensitiveType> c) {
        SensitiveDataConverter.keys.putAll(c);
    }

    public static void setEnable(boolean enable) {
        SensitiveDataConverter.enable = enable;
    }


    public static void addIncludePackages(Collection<String> c){
        includePackages.addAll(c);
    }

    /**
     * 转换
     *
     * @param event
     * @return
     */
    @Override
    public String convert(ILoggingEvent event) {

        String formatMsg = event.getFormattedMessage();
        if (StringUtils.isBlank(formatMsg)) {
            return formatMsg;
        }
        try {

            String msg = event.getFormattedMessage();

            // 未开启开关则直接返回日志
            if (!enable) {
                return formatMsg;
            }

            // 消息为空则无需处理
            if (StringUtils.isEmpty(msg)) {
                return formatMsg;
            }
            if(CollUtil.isEmpty(keys)){
                return formatMsg;
            }
            String loggerName = event.getLoggerName();

            if(!includePackages.stream().anyMatch(loggerName::startsWith)){
                return formatMsg;
            }


            // 参考 DefaultStringMessageDefender

            StringBuilder buffer=new StringBuilder(msg);
            String keyValueStr;

            int initLength = 0;
            int messageLength = msg.length();
            // 一个flag, 代表了本次key-value的脱敏方式
            final KeyValueDelimiter[] strideFlag = new KeyValueDelimiter[1];

            for (String key : keys.keySet()) {
                for (int currKeyCurrIndex = initLength; currKeyCurrIndex < messageLength; ) {
                    strideFlag[0] = null;
                    int keyStartIndex = msg.indexOf(key, currKeyCurrIndex);
                    if (keyStartIndex == -1) {
                        currKeyCurrIndex = messageLength;
                        continue;
                    }
                    boolean skip =
                            keyStartIndex != 0 && !PRE_DELIMITER_PATTERN.matcher(msg.charAt(keyStartIndex - 1) + "").matches();
                    if (skip) {
                        currKeyCurrIndex = keyStartIndex + key.length();
                        continue;
                    }
                    int commaIndex = msg.indexOf(",", keyStartIndex + 1);
                    if (commaIndex == -1) {
                        commaIndex = messageLength;
                    }
                    keyValueStr = msg.substring(keyStartIndex, commaIndex);
                    doDesensitization(keyValueStr, key, keyStartIndex, buffer, keys.get(key),strideFlag);

                    // 如果处理方式是json, 那么步长为key的长度, 否者步长为key-value的长度
                    if (strideFlag[0] == KeyValueDelimiter.COLON) {
                        currKeyCurrIndex = keyStartIndex + key.length();
                    } else {
                        currKeyCurrIndex = keyStartIndex + keyValueStr.length();
                    }
                }
            }

            return buffer.toString();
        } catch (Exception e) {
            log.error("日志处理错误:{}", formatMsg);
            return formatMsg;
        }
    }

    /**
     * 脱敏
     *
     * @param keyAndValueStr
     *         本次要脱敏的 key-value， 如: customId = 123
     * @param keyStr
     *         本次要脱敏的key-value的key
     * @param keyStartIndex
     *         key的位置
     * @param sb
     *         脱敏(后的)数据容器
     * @param sensitiveType
     *         脱敏的策略
     * @param strideFlag
     *         一个长度为1的数组, 用于记录本次定位key-value中value的方式
     */
    private void doDesensitization(String keyAndValueStr, String keyStr, int keyStartIndex,
                                   StringBuilder sb, SensitiveType sensitiveType, KeyValueDelimiter[] strideFlag) {
        int keyLength = keyStr.length();
        String valueStr = getValue(keyAndValueStr, keyLength, strideFlag);
        if (valueStr == null) {
            return;
        }
        int valueStrStartRelativeIndex = keyAndValueStr.indexOf(valueStr, keyLength);
        String valueAfterDesensitization =SensitiveInfoUtils.desensitizedLog(sensitiveType,valueStr);
        int replaceStartIndex = keyStartIndex + valueStrStartRelativeIndex;
        int replaceEndIndex = replaceStartIndex + valueStr.length();
        sb.replace(replaceStartIndex, replaceEndIndex, valueAfterDesensitization);

    }

    /**
     * 从键值字符串中，获取到value。
     *
     * @param keyAndValueStr
     *         键值字符串， 如: customId = 123
     * @param keyLength
     *         键的长度
     * @param strideFlag
     *         一个长度为1的数组, 用于记录本次定位key-value中value的方式
     *
     * @return 返回value值。 如果返回null, 说明无法正常获取到value。
     */
    protected String getValue(String keyAndValueStr, int keyLength, KeyValueDelimiter[] strideFlag) {
        if (keyAndValueStr.length() <= keyLength) {
            return null;
        }

        boolean isValid;
        String delimiter= "->";
        /// 以 箭头符号【->】 为key-value分隔符
        int arrowSignIndex = keyAndValueStr.indexOf(delimiter);
        if (arrowSignIndex != -1) {
            // 排除value值中的 箭头符号对逻辑的影响(简单实现)
            isValid = SPACE_PATTERN.matcher(keyAndValueStr.substring(keyLength, arrowSignIndex)).matches();
            if (isValid) {
                strideFlag[0] = KeyValueDelimiter.ARROW;
                return keyAndValueStr.substring(arrowSignIndex + delimiter.length()).trim();
            }
        }

        /// 以 等于符号【=】 为key-value分隔符
        delimiter = "=";
        int equalSignIndex = keyAndValueStr.indexOf(delimiter);
        if (equalSignIndex != -1) {
            // 排除value值中的等号对逻辑的影响(简单实现)
            isValid = SPACE_PATTERN.matcher(keyAndValueStr.substring(keyLength, equalSignIndex)).matches();
            if (isValid) {
                strideFlag[0] = KeyValueDelimiter.EQUAL;
                return keyAndValueStr.substring(equalSignIndex + delimiter.length()).trim();
            }
        }

        /// json字符串形式的key-value分隔
        delimiter = ":";
        int colonSignIndex = keyAndValueStr.indexOf(delimiter);
        // 如果是json格式的话，key后面紧接着的应该是【"】
        boolean nextCharIsLegalAfterKey = '\"' == keyAndValueStr.charAt(keyLength);
        int length = 1;
        boolean isBackslashQuotation = false;
        if (!nextCharIsLegalAfterKey && colonSignIndex != -1 ) {
            String tmpStr = keyAndValueStr.substring(keyLength, colonSignIndex).trim();
            if (BACKSLASH_QUOTATION_SIGN.equals(tmpStr)) {
                length = 2;
                isBackslashQuotation = true;
                nextCharIsLegalAfterKey = true;
            }
        }
        if (colonSignIndex != -1 && nextCharIsLegalAfterKey) {
            // 排除value值中的前进符号对逻辑的影响(简单实现)
            isValid = SPACE_PATTERN.matcher(keyAndValueStr.substring(keyLength + length, colonSignIndex)).matches();
            if (isValid) {
                String valueStr = keyAndValueStr.substring(colonSignIndex + delimiter.length()).trim();
                // 脱敏粒度 至 简单的key-value
                // 且，如果简单的key-value中,值是一个json样式的字符串，如:"key": "{\"key1\":\"value1\",\"key2\":\"value2\"}"
                // 那么此粒度也不脱敏
                if (StringUtils.startsWithAny(valueStr, OBJECT_PREFIX_SIGN,
                        ARRAY_PREFIX_SIGN, JSON_STRING_SIGN)) {
                    return null;
                }
                int valueStrLength = valueStr.length();
                // 如果这个字段刚好处于json的最后面，那么可能会多截取到【}】或【}]】之类的，所以需要判断值是String还是Number
                int substringStartIndex = isBackslashQuotation ?
                        (valueStr.startsWith(BACKSLASH_QUOTATION_SIGN) ? 2 : 0)
                        : (valueStr.startsWith(QUOTATION_SIGN) ? 1 : 0);
                int substringEndIndex = isBackslashQuotation ?
                        (valueStr.startsWith(BACKSLASH_QUOTATION_SIGN) ?
                                valueStr.lastIndexOf(BACKSLASH_QUOTATION_SIGN) : valueStrLength)
                        : (valueStr.startsWith(QUOTATION_SIGN) ?
                        valueStr.lastIndexOf(QUOTATION_SIGN) : valueStrLength);
                valueStr = valueStr.substring(substringStartIndex, substringEndIndex);
                strideFlag[0] = KeyValueDelimiter.COLON;
                return valueStr;
            }
        }

        /// 如果不是使用上面的key-value分隔符，那么返回null
        return null;
    }







    /**
     * 本次key-value使用的脱敏方式： :  =  ->
     */
    public enum KeyValueDelimiter {

        /** : (同json key-value分隔符) */
        COLON,

        /** = */
        EQUAL,

        /** -> */
        ARROW
    }

}
